﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using Tiwo.Design.Renderers;
using Tiwo.Design.Trackers;

namespace Tiwo.Design
{
    public partial class SchemeRenderSite : Panel, ISchemeRenderer
    {
        public SchemeRenderSite()
        {
            InitializeComponent();

            SetStyle(
                ControlStyles.ResizeRedraw |
                ControlStyles.UserPaint |
                ControlStyles.AllPaintingInWmPaint |
                ControlStyles.OptimizedDoubleBuffer |
                ControlStyles.DoubleBuffer,
                true);
        }

        public PointF LastClickLocation { get; set; }

        public event EventHandler<RendererEventArgs> ItemHover;
        public event EventHandler<RendererEventArgs> ItemLeave;
        public event EventHandler<EventArgs> ShowContextMenu;
        public event EventHandler<ActionListEventArgs> ShowActionMenu;

        private readonly List<IRenderer> _renderers = new List<IRenderer>();

        private TrackerRuntime Tracker { get; set; }

        public IRenderer RendererUnderMouse { get; private set; }
        public IMouseTracker TrackerUnderMouse { get; private set; }

        public void Add(IRenderer renderer)
        {
            _renderers.Add(renderer);
            Invalidate();
        }

        protected override void OnPaintBackground(PaintEventArgs e) { }

        protected override void OnPaint(PaintEventArgs e)
        {
            e.Graphics.Clear(BackColor);

            using (var cache = new RenderingCache())
            {
                cache.DefaultFont = Font;
                _renderers.ForEach(renderer => RendererDrawTo(renderer, e.Graphics, cache));
            }
        }

        private void RendererDrawTo(IRenderer renderer, Graphics gr, IRenderingCache cache)
        {
            renderer.DrawTo(gr, cache);

            if (Tracker == null)
            {
                if (RendererUnderMouse == renderer && TrackerUnderMouse != null)
                    TrackerUnderMouse.DrawTo(gr, cache);
            }
            else
            {
                if (renderer == Tracker.MouseTrackerRenderer)
                    TrackerUnderMouse.DrawTo(gr, cache);
            }
        }

        private IRenderer FindRendererUnderPoint(Point location)
        {
            return _renderers.LastOrDefault(renderer => renderer.HitTest(location));
        }

        protected override void OnMouseLeave(EventArgs e)
        {
            base.OnMouseLeave(e);

            TrackRendererHover(null, Point.Empty);
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);

            LastClickLocation = e.Location;

            if (e.Button == MouseButtons.Left)
            {
                TrackRendererMouseDown(e.Location);
            }

            if (e.Button == MouseButtons.Right)
            {
                ShowRendererActions();
            }
        }

        private void ShowRendererActions()
        {
            if (Tracker != null) return;
            if (RendererUnderMouse == null)
            {
                Fire(ShowContextMenu, EventArgs.Empty);
                return;
            }

            var actionprovider = RendererUnderMouse.ActionProvider;
            if (actionprovider == null) return;

            var actionList = actionprovider.CreateActions();
            if (actionList.IsEmpty) return;

            Fire(ShowActionMenu, new ActionListEventArgs(actionList));
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

            TrackRendererMouseMove(e.Location);
            TrackRendererHover(FindRendererUnderPoint(e.Location), e.Location);
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            base.OnMouseDown(e);

            TrackRendererMouseUp(e.Location);
        }

        private void TrackRendererMouseMove(Point location)
        {
            if (Tracker != null)
                Tracker.MouseTracker.MouseMove(location);
        }

        private void TrackRendererMouseDown(Point location)
        {
            if (RendererUnderMouse == null) return;
            if (RendererUnderMouse.Modificator == null) return;

            var tracker = RendererUnderMouse.Modificator.CreateMouseTracker(location);
            if (tracker == null) return;

            Tracker = new TrackerRuntime
            {
                MouseTracker = tracker,
                MouseTrackerRenderer = RendererUnderMouse
            };

            Tracker.MouseTracker.Site = this;
            Tracker.MouseTracker.MouseDown(location);
        }

        private void TrackRendererMouseUp(Point location)
        {
            if (Tracker == null) return;

            Tracker.MouseTracker.MouseUp(location);
            Tracker = null;
        }

        private void TrackRendererHover(IRenderer renderer, Point location)
        {
            if (Tracker != null) return;

            if (renderer == null && RendererUnderMouse != null)
            {
                Fire(ItemLeave, RendererUnderMouse);
            }

            TrackerUnderMouse = null;
            RendererUnderMouse = renderer;

            if (RendererUnderMouse != null)
            {
                Fire(ItemHover, RendererUnderMouse);
                TrackerUnderMouse = RendererUnderMouse.Modificator.CreateMouseTracker(location);
            }

            Cursor = TrackerUnderMouse != null ? TrackerUnderMouse.Cursor : DefaultCursor;
            Invalidate();
        }

        private void Fire(EventHandler<RendererEventArgs> handler, IRenderer renderer)
        {
            Fire(handler, new RendererEventArgs(renderer));
        }

        private void Fire<T>(EventHandler<T> handler, T args) where T : EventArgs
        {
            if (handler == null)
                return;
            handler(this, args);
        }

        private class TrackerRuntime
        {
            public IMouseTracker MouseTracker { get; set; }
            public IRenderer MouseTrackerRenderer { get; set; }
        }
    }
}
